using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;

namespace Satire.Common
{
    public class TableBuilder<T>
    {
        public TableBuilder(int cols)
        {
            _cols = cols;
        }

        public void addRow(T[] row) 
        {
            // Check to see that the row contains the agreed number of rows.
            if (row.Length != _cols)
            {
                throw new Exception("Mismatch in row length");
            }

            // Calculate the hash code for the row to add. XOR algorithm is used.
            int hashCode = 0xABCD;
            foreach (T c in row)
            {
                hashCode = _hashFunc(c.GetHashCode(), hashCode);
            }

            // Check to see if hash code exists in the Dictionary of hash codes.
            List<int> RowsWithHash = null;
            int newRowCoreIdx = -1;
            if (_hash.TryGetValue(hashCode, out RowsWithHash))
            {
                // It does, so loop through each row and check to see if we have a match
                foreach (int rwh in RowsWithHash)
                {
                    newRowCoreIdx = rwh;
                    for(int i = 0; i < _rowCore[rwh].Length; i++) 
                    {
                        if (!_rowCore[rwh][i].Equals(row[i]))
                        {
                            // A difference in one element means we do not have a match,
                            // i.e. same hashcode, but not equal - indicates poor hash.
                            newRowCoreIdx = -1;
                            break;
                        }
                    }
                    if (newRowCoreIdx != -1)
                    {
                        break;
                    }
                }
            }

            // If hash is not in table, we neet to add the hashcode entry.
            if (RowsWithHash == null)
            {
                RowsWithHash = new List<int>();
                _hash.Add(hashCode, RowsWithHash);
            }

            // So hash exists, but no identical row, so we have to add the new row to core,
            // add the hashcode to the hascode entry.
            if (newRowCoreIdx < 0)
            {
                _rowCore.Add((T[]) row.Clone());
                newRowCoreIdx = _rowCore.Count - 1;
                RowsWithHash.Add(newRowCoreIdx);
            }

            // Set up the row translation table to point the new row to the entry in code
            _rowXref.Add(newRowCoreIdx);

        }

        public Table<T> GenerateTable()
        {
            // Create the table object
            Table<T> table = new Table<T>();

            // The rows have already been compressed, we now have to compress the columns
            // Set up the variables for the column compression.
            _hash.Clear();
            table._colXref = new int[_cols];
            List<int> colCore = new List<int>();

            // main loop to move through columns.
            for (int c = 0; c < _cols; c++)
            {
                // calculate hash for column
                int hashCode = 0xABCD;
                for (int r = 0; r < _rowCore.Count; r++)
                {
                    hashCode = _hashFunc(hashCode, _rowCore[r][c].GetHashCode());
                }

                // Check to see if hashcode already exists in the Dictionary of hash codes.
                List<int> ColsWithHash = null;
                int newColCoreIdx = -1;
                if (_hash.TryGetValue(hashCode, out ColsWithHash))
                {
                    // It does, so loop through each column and check to see if we have a match
                    foreach (int cwhc in ColsWithHash)
                    {
                        newColCoreIdx = cwhc;
                        int cwh = colCore[cwhc];
                        for (int i = 0; i < _rowCore.Count; i++)
                        {
                            if (!_rowCore[i][cwh].Equals(_rowCore[i][c]))
                            {
                                // A difference in one element means we do not have a match,
                                // i.e. same hashcode, but not equal - indicates poor hash.
                                newColCoreIdx = -1;
                                break;
                            }
                        }
                        if (newColCoreIdx != -1)
                        {
                            break;
                        }
                    }
                }

                // If hash is not in table, we neet to add the hashcode entry.
                if (ColsWithHash == null)
                {
                    ColsWithHash = new List<int>();
                    _hash.Add(hashCode, ColsWithHash);
                }

                // So hash exists, but no identical column, so we have to add the new column to core,
                // add the hashcode to the hascode entry.
                if (newColCoreIdx < 0)
                {
                    newColCoreIdx = colCore.Count;
                    colCore.Add(c);
                    ColsWithHash.Add(newColCoreIdx);
                }

                // Set up the column translation table to point the new column to the entry in code
                table._colXref[c] = newColCoreIdx;

                
            }

            // Build the row xref
            table._rowXref = _rowXref.ToArray();

            // Build the core space for the table
            table._core = new T[_rowCore.Count, colCore.Count];

            // Copy individual values across
            for (int r = 0; r < _rowCore.Count; r++)
            {
                for (int c = 0; c < table._colXref.Length; c++)
                {
                    table._core[r, table._colXref[c]] = _rowCore[r][c];
                }
            }

            // Return the newly created table
            return table;
        }

        #region Private
        private int _hashFunc(int i1, int i2)
        {
            return (i1 % 0xff) + (i2 % 0xff);
        }

        private int _cols;
        private List<T[]> _rowCore = new List<T[]>();
        private List<int> _rowXref = new List<int>();
        private Dictionary<int, List<int>> _hash = new Dictionary<int, List<int>>();
        #endregion
    }

    public class Table<T>
    {
        internal Table() { }

        public T this[int r, int c] { get { return _core[_rowXref[r], _colXref[c]]; } }
        public int CoreRowCount { get { return _core.GetLength(0); } }
        public int CoreColCount { get { return _core.GetLength(1); } }
        public int RowCount { get { return _rowXref.Length; } }
        public int ColCount { get { return _colXref.Length; } }

        #region Private & Internal
        internal T[,] _core = null;
        internal int[] _rowXref = null;
        internal int[] _colXref = null;
        #endregion
    }
}
